#ifndef MESH_OBJECT_H_
#define MESH_OBJECT_H_

#include <iostream>
#include <cassert>
using namespace std;

#include <irrlicht/irrArray.h>
#include <lib3ds.h>

// opengl includes
#include <gl/gl.h>
#include <gl/glu.h>
//#include <gl/glaux.h>

#include "ObjectNode.h"



class MeshObject : public ObjectNode
{
protected:
	Lib3dsFile *file; /**< file holds the data of the model */
	const char *filename; /**< It's the filename of the model */
	int curFrame; /**< curFrame keeps track of the current frame that shuold be renderd */
	irr::core::array<GLuint> textureIndices; /**< this variable holds the texture indicies */
	bool lightEnabled; /**< wheter light was enabled before this class. */
	GLuint lightListIndex;
	
public:
	const static int TYPE = 4;

	MeshObject(SceneGraphNode * parent = NULL, const char * filename = NULL, bool ownsChildren = false) :
		ObjectNode(parent, DRAW_MIDDLE, NULL, -1, ownsChildren)
	{
		file = 0;

		if (filename)
		{
			loadFromFile(filename);
		}
		
		this->type |= MeshObject::TYPE;
	}
	
	~MeshObject()
	{
	}
	
	bool loadFromFile(const char * filename)
	{
		this->filename = filename;

		cout << "Attempting to load " << this->filename << "...";

		// load the 3ds file
		file = lib3ds_file_open(filename);
		
		if (!file)
		{
			return false;
		}
		
		cout << "done.\n";

		// create nodes for meshes
		if (!file->nodes)
		{
			cout << "File does not have nodes - creating...";
			lib3ds_file_create_nodes_for_meshes(file);
			cout << "done - file->nodes is now " << file->nodes << ".\n";
		}
		
		Lib3dsNode * node = file->nodes;
		for (;node != NULL; node = node->next)
		{
			node->user_id = 0;
		}

		file->lights = 0;

		// evaluate the file
		cout << "Evaluating file...";
		lib3ds_file_eval(file, 0);
		cout << "done.\n";
		
		Lib3dsMesh * mesh;
		
		cout << "File has " << file->nmeshes << " meshes.\n";
		
		for (int i = 0; i < file->nmeshes; i++)
		{
			mesh = file->meshes[i];
			if (mesh->texcos)
			{
				cout << "Applying textures for mesh " << i << "...";
				if (!applyTexture(mesh))
				{
					return false;
				}
				cout << "Done.\n";
			}
		}
		
		if (file->lights)
		{
			cout << "File has lights - creating list...";
			if (!createLightList())
			{
				return false;
			}
			cout << "done.\n";
		}
		
		return true;
	}
	
	bool createLightList()
	{
		lightListIndex = glGenLists(1);
		glNewList(lightListIndex, GL_COMPILE_AND_EXECUTE);
		Lib3dsLight * light; // temporary variable to store our lights
		GLint curlight = GL_LIGHT0;
		for(int i = 0; i < file->nlights; i++)
		{
			light = file->lights[i];
			
			if(light->off) //if our light is off
			{
				continue; //Move to the next light
			}
			
			GLfloat diff[4], amb[4], pos[4];
			
			for(int j = 0;j < 3;j++)
			{
				diff[j] = light->color[j];
				amb[j] = light->color[j] / 4.5; // We might wanna change this;
				pos[j] = light->position[j];
			}
			
			diff[3] = amb[3] = pos[3] = 1.0;
			
			glEnable(curlight);
			curlight++;
			// Start setting light
			glLightfv(GL_LIGHT0, GL_DIFFUSE, diff); //set the diffuse color
			glLightfv(GL_LIGHT0, GL_POSITION, pos); //set the position of the light
			glLightfv(GL_LIGHT0, GL_AMBIENT, amb); // set the ambient color of the light
			glLightfv(GL_LIGHT0, GL_SPECULAR, diff); // set the specular color of the light
		
			if(light->spot_light) // if it's a light spot
			{
				for(int i = 0;i < 3;i++) // get position of the light
				{
					pos[i] = light->target[i] - light->position[i];
				}
				pos[3] = light->target[3] - light->target[3];
				glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, pos); //specify that we have a spot at position 'pos'
			}
		}
		glEndList();
		
		return true;
	}
	
	bool applyTexture(Lib3dsMesh * mesh)
	{
		if(mesh->texcos) //if we have texels
		{
			GLuint tmpIndex; // temporary index to old the place of our texture
			glGenTextures(1, &tmpIndex); // allocate memory for one texture
			textureIndices.push_back(tmpIndex); // add the index of our newly created texture to textureIndices
			glBindTexture(GL_TEXTURE_2D, textureIndices[(textureIndices.size()-1)]); // use our newest texture
			//gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, (int)pow(mesh->map_planar_size[0], 0.5), //square root of pixels
			//	(int)pow(mesh->map_planar_size[1], 0.5), GL_RGB, GL_FLOAT, 
			//	mesh->texelL); // genereate MipMap levels for our texture 
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // give the best result for texture magnification
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //give the best result for texture minification
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // don't repeat texture 
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // don't repeat texture
			
			return true;
		}
		return false;
	}
	
	void listInstructions(Lib3dsMesh * mesh)
	{
		unsigned p;
		float (*normals)[3];
		normals = static_cast<float(*)[3]> (std::malloc (20*sizeof(float)*mesh->nvertices)); //alocate memory for our normals
		{
			float m[4][4];
			lib3ds_matrix_copy(m, mesh->matrix); //copy the matrix of the mesh in our temporary matrix
			lib3ds_matrix_inv(m);
			glMultMatrixf(&m[0][0]); //adjust our current matrix to the matrix of the mesh

		}
		lib3ds_mesh_calculate_vertex_normals(mesh, normals); //calculate the normals of the mesh
		int curIndex = 0;

		if(mesh->texcos)
		{
			if(! glIsEnabled(GL_TEXTURE_2D))
			{
				glEnable(GL_TEXTURE_2D);
			}

			int j = 0;

			for(int i = 0; i < file->nmeshes; i++)
			{
				Lib3dsMesh *tmpmesh = file->meshes[i];
				if(strcmp(tmpmesh->name, mesh->name) == 0)
				{
					break;
				}
				else if(tmpmesh->texcos)
				{
					j++;
				}
			}
			glBindTexture(GL_TEXTURE_2D, textureIndices[j]);
			curIndex++;
		}

		for(p = 0;p < mesh->nfaces;p++)
		{
			Lib3dsFace *f = &mesh->faces[p];
			Lib3dsMaterial *mat=0;
			if(f->material > -1) //if the face of the mesh has material properties
			{
				//int index = lib3ds_file_material_by_name(file, f->material); //read material properties from file
				mat = file->materials[f->material];
			}
			if(mat) //if we have material
			{
				static GLfloat ambient[4] = { 0.0, 0.0, 0.0, 1.0 };
				glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); // Ambient color
				glMaterialfv(GL_FRONT, GL_DIFFUSE, mat->diffuse); //diffuse color
				glMaterialfv(GL_FRONT, GL_SPECULAR, mat->specular); //specular color
				float shine;
				shine = pow(2, 10.0 * mat->shininess);
				if(shine > 128.0)
				{
					shine = 128.0;
				}
				glMaterialf(GL_FRONT, GL_SHININESS, shine);
			}
			else // if we do not have material properties, we have to set them manually
			{
				GLfloat diff[4] = { 0.75, 0.75, 0.75, 1.0 }; // color: white/grey
				GLfloat amb[4] = { 0.25, 0.25, 0.25, 1.0 }; //color: black/dark gray
				GLfloat spec[4] = { 0.0, 0.0, 0.0, 1.0 }; //color: completly black
				glMaterialfv(GL_FRONT, GL_DIFFUSE, diff);
				glMaterialfv(GL_FRONT, GL_AMBIENT, amb);
				glMaterialfv(GL_FRONT, GL_AMBIENT, spec);
			}
			{
				float matrix[16];
				glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
				glBegin(GL_TRIANGLES);
				for(int i = 0;i < 3;i++)
				{
					glNormal3fv(normals[f->index[i]]); //set normal vector of that point
					glVertex3fv(mesh->vertices[f->index[i]]); //Draw point of the mesh
					//totalVertexCount++;

					//cout << mesh->vertices[f->index[i]][0] << ", " <<
					//	mesh->vertices[f->index[i]][1] <<  ", " <<
					//	mesh->vertices[f->index[i]][2] <<
					//	endl;
				}
				glEnd();
			}
		}
		free(normals); //free up memory
	}

	bool renderNode(Lib3dsNode * node)
	{
		assert(file); //this is for debugging
		{
			Lib3dsNode *tmp;
			for(tmp = node->childs;tmp != 0;tmp = tmp->next)
			{
				renderNode(tmp); //render all child nodes of this note
			}
		}
		if(node->type == LIB3DS_NODE_MESH_INSTANCE) //check wheter the node is a 3ds node
		{
			if(!node->user_id) //Wheter we have a list or not, if not we're gonna create one
			{
				cout << "Generating a list for " << this->filename << "...";
				int index = lib3ds_file_mesh_by_name(file, node->name); //get all the meshes of the current node
				assert(index > -1); //for debugging in case we don't have a mesh
				
				if(index < 0)
				{
					return false;
				}
				
				Lib3dsMesh *mesh = file->meshes[index];
				node->user_id = glGenLists(1); //alocate memory for one list
				/////////////////////////////////////////////////////////////	
				if(glGetError() != GL_NO_ERROR)
				{
					cout << "ERROR!\n";
					return false;
				}
				
				int totalVertexCount = 0;

				/////////////////////////////////////////////////////////////	
				glNewList(node->user_id, GL_COMPILE); //here we create our list
				{
					listInstructions(mesh);
				}
				glEndList(); // end of list
				cout << "done with " << totalVertexCount << " vertices.\n";
			}

			if(node->user_id) // if we have created a link list(with glNewList)
			{

				// convert to a mesh instance node
				Lib3dsMeshInstanceNode * min = (Lib3dsMeshInstanceNode*) node;
				
				glPushMatrix(); //save transformation values
				glTranslatef(-min->pivot[0], -min->pivot[1], -min->pivot[2]); //move to the right place;
				glMultMatrixf(&node->matrix[0][0]); //adjust matrix according to the node
				
				float matrix[16];
				glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
				glCallList(node->user_id); //render node

				glPopMatrix(); //return transformation original values
			}
		}
		return true;
	}
	
	void render()
	{
		if (!file)
		{
			return;
		}

		glPushMatrix(); // save transformation values
		GLfloat gl_matrix[16];
		memcpy ( gl_matrix, absolute.pointer(), 16 * sizeof(irr::f32) );
		glMultMatrixf(gl_matrix);
		
		if (file->lights) //if we have lights in the model
		{
			enableLights(); //enable all lights
			glCallList(lightListIndex); //set lights.
		}
		
		Lib3dsNode *nodes;
		for(nodes = file->nodes; nodes != 0; nodes = nodes->next) // Render all nodes
		{
			renderNode(nodes);
		}
			
		glPopMatrix();
		
		if(file->lights)
		{
			disableLights(); // disable lighting, we don't want it have it enabled longer than necessary
		}
		
		curFrame++;
		if(curFrame > file->frames) //if the next frame doesn't exist, go to frame 0
			curFrame = 0;
		lib3ds_file_eval(file, curFrame); // set current frame
	}

	void enableLights()
	{
		GLuint lightNum = GL_LIGHT0;
		Lib3dsLight *light;
		for(int i = 0; i < file->nlights; i++)
		{
			light = file->lights[i];
			if(!glIsEnabled(lightNum))
				glEnable(lightNum);
			lightNum++;
		}
	}

	void disableLights()
	{
		GLuint lightNum = GL_LIGHT0;
		Lib3dsLight *light;
		for(int i = 0; i < file->nlights; i++)
		{
			light = file->lights[i];
			if(glIsEnabled(lightNum))
				glDisable(lightNum);
			lightNum++;
		}
	}
};


#endif
